/*
    Copyright 2010 Northbranchlogic, Inc.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
 */

package com.norbl.runjrun.gui;

import com.norbl.runjrun.*;
import javax.swing.*;
import java.awt.event.*;
import java.lang.reflect.*;
import com.norbl.util.*;
import com.norbl.util.ec2.*;
import com.norbl.util.s3.*;
import com.norbl.util.sqs.*;
import com.norbl.util.sdb.*;
import java.util.*;

/**
 *
 * @author Barnet Wagman
 */
public class Actions extends NodeTaskOps implements ActionListener {

    public TaskClickHandler taskClickHandler;
    public NodeClickHandler nodeClickHandler;

    private enum MenuType { nodeAction, taskAction, 
                            singleTaskAction, singleNodeAction };

    HashMap<String,Op> opHt;
    List<Op> ops;

    StateTableModels stateTableModels;

    private PopupCommandTask popupCommandTask;
    private PopupCommandNode popupCommandNode;

    public Actions(Ec2Ops eops, S3Ops sop, SdbOps dops, SqsOps qops,
                   NodeStateTable nodeStateTable,
                   TaskStateTable taskStateTable,
                   String inputBucketName,
                   String inputS3KeyPrefix,
                   StateTableModels stateTableModels) {
        super();
        super.init(eops, sop, dops, qops, nodeStateTable, taskStateTable,
                   inputBucketName,inputS3KeyPrefix);
        taskClickHandler = new TaskClickHandler();
        nodeClickHandler = new NodeClickHandler();
        this.stateTableModels = stateTableModels;
        popupCommandTask = new PopupCommandTask();
        popupCommandNode = new PopupCommandNode();

        createOps();
    }

    void addNodeOpActions(JMenu menu) {

        for ( Op op : ops ) {
            if ( op.menuType.equals(MenuType.nodeAction) ) {
                menu.add(createMenuItem(op.actionCommand,
                                        op.menuItemName,
                                        op.toolTip));
            }
        }
    }

    void addTaskOpActions(JMenu menu) {

        for ( Op op : ops ) {
            if ( op.menuType.equals(MenuType.taskAction) ) {
                menu.add(createMenuItem(op.actionCommand,
                                        op.menuItemName,
                                        op.toolTip));
            }
        }
    }

    private JMenuItem createMenuItem(String actionCommand,
                                     String menuItemLabel,
                                     String toolTip) {

        JMenuItem mi = new JMenuItem();
        mi.setActionCommand(actionCommand);
        mi.setText(menuItemLabel);
        mi.setToolTipText(toolTip);
        mi.addActionListener(this);
        return(mi);
    }

    public void actionPerformed(ActionEvent ev) {

        final Op op = opHt.get(ev.getActionCommand());
        if ( op == null ) {           
            return;
        }

        final Class[] argTypes;
        final Object[] args;

        if (  MenuType.singleTaskAction.equals(op.menuType) ) {
            argTypes = new Class[] { TaskState.class };
            args = new Object[] { popupCommandTask.s };
        }
        else if (  MenuType.singleNodeAction.equals(op.menuType) ) {
            argTypes = new Class[] { NodeState.class };
            args = new Object[] { popupCommandNode.s };
        }
        else {
            argTypes = new Class[] {};
            args = new Object[] {};
        }

        if ( op.promptMessage != null ) {
            if ( !GuiUtil.answerIsYes(op.promptMessage,op.menuItemName) )
                return;
        }

        (new Thread(new Runnable() {
            public void run() {
                try {
                    Method m = Actions.class.getMethod(op.methodName,
                                                       argTypes);
                    TaskInProgressWindow tip =
                        TaskInProgressWindow.run("RunJRun Monitor",
                            "In progress",
                            "<html>" + op.menuItemName + "<br> is running.");
                    m.invoke(Actions.this,args);
                    tip.dispose();
                }
                catch(Exception xxx) {
                    System.out.println(StringUtil.throwableToString(xxx));
            }
            }})).start();
    }

         // ----------------------------------------

    public class NodeClickHandler extends MouseAdapter {

        JTable jTable;

        NodeClickHandler() {}

        public void setJTable(JTable jTable) {
            this.jTable = jTable;
            this.jTable.addMouseListener(this);
        }

        public void mousePressed(MouseEvent e) {

            int index = jTable.rowAtPoint(e.getPoint());
            String hostName = (String)
                stateTableModels.nodeStateTableModel.getValueAt(index,0);

            handleNodeSelection(hostName,e);
        }
    }

    public class TaskClickHandler extends MouseAdapter {

        JTable jTable;

        TaskClickHandler() {}

        public void setJTable(JTable jTable) {
            this.jTable = jTable;
            this.jTable.addMouseListener(this);
        }

        public void mousePressed(MouseEvent e) {

            int index = jTable.rowAtPoint(e.getPoint());
            String taskName = (String)
                stateTableModels.taskStateTableModel.getValueAt(index,0);

            handleTaskSelection(taskName,e);

        }
    }

    class Op {
        String actionCommand;
        String methodName;
        String menuItemName;
        String toolTip;
        String[] promptMessage;
        MenuType menuType;

        Op(String methodName, String menuItemName, String toolTip,
           String[] promptMessage, MenuType menuType) {

            this.methodName = methodName;
            this.menuItemName = menuItemName;
            this.toolTip = toolTip;
            this.promptMessage = promptMessage;
            this.menuType = menuType;
            this.actionCommand = methodName + "_" + menuType.toString();
        }                
    }

    private String singleTaskAC(String methodName) {
        return(methodName + "_" + MenuType.singleTaskAction.toString());
    }

    private String singleNodeAC(String methodName) {
        return(methodName + "_" + MenuType.singleNodeAction.toString());
    }
    
    private void createOps() {

        opHt = new HashMap<String, Op>();
        ops = new ArrayList<Op>();

            // --------- Task ops -----------------------------------

        ops.add(new Op("clearFinishedTasks",
                       "Clear finished tasks",
                        "Removes tasks with state 'finished' from the " +
                        "task table.",
                        new String[] {
                        "Remove finished tasks from the task table?"
                        },
                        MenuType.taskAction));

        ops.add(new Op("deleteWaitingTasks",
                       "Delete waiting tasks",
                        "Removes tasks with state 'waiting' from the " +
                        " SQS message queue and from the task table; " +
                        " this operation does not delete raw Runnables.",
                        new String[] {
                        "Remove waiting tasks from the sqs task queue " +
                        "and the task table?"
                        },
                        MenuType.taskAction));

        ops.add(new Op("clearAllTasksEverywhere",
                        "Clear all tasks",
                        "<html>Deletes all task messages from the sqs queue,<br>" +
                        "all raw task objects from S3, and all task states from<br>" +
                        "the sdb task table.  Note that a running task is not<br>" +
                        "removed from it's node.  Cooked tasks in S3 are not<br>" +
                        "affected",
                        new String[] {
                        "Delete all task messages in the SQS input queue,",
                        "all raw task objects in S3 input directory,",
                        "and all task states from the SDB table?"
                        },
                        MenuType.taskAction));
        
            // ---------  Node ops ---------------------------------------


        ops.add(new Op("clearDeadInstances", // method name
                       "Clear terminated instance nodes",
                       "Remove nodes from the node table whose " +
                       "ec2 instances are no longer running.",
                       new String[] {
                        "Remove nodes from the node table whose ec2",
                        "instances are no longer running?"},
                        MenuType.nodeAction));

        ops.add(new Op("terminateInstances", // method name
                       "Terminate all instances",
                       "Terminates all ec2 instances that are listed in " +
                       "the node table (i.e. that are running " +
                       "RunJRun RunnerNodes).",
                       new String[] {
                        "Terminate all ec2 instances listed in the node table?"},
                        MenuType.nodeAction));

        ops.add(new Op("rebootDefunctNodeInstances", // method name
                       "Reboot instances with non-functioning nodes",
                       "Reboot all instances whose RunnerNodes " +
                       "appear to have died - i.e. nodes with state " +
                       "'no_pulse' or 'nil'.",
                       new String[] {
                       "Reboot ec2 instances with non-functioning instances",
                       "of RunnerNode (i.e. status 'no_pulse' or 'nil'?"},
                        MenuType.nodeAction));

        ops.add(new Op("clearAllNodes",
                       "Clear all nodes",
                       "<html>Removes all nodes from the node state table.<br>" +
                        "This op has no effect on the actual <tt>RunnerNode</tt><br>" +
                        "instances or the hosts they are running on.  E.g. it does not<br>" +
                        "terminate the ec2 instances.<p>" +
                        "Note that a running <tt>RunnerNode</tt> will reappear<br>" +
                        "after being cleared, since <tt>RunnerNode</tt> periodically<br>" +
                        "sends 'pulse' messages to the sdb node table.</html>",
                       new String[] {"Remove all nodes from the SDB table?" },
                       MenuType.nodeAction));

            // ------- Single task ops ------------------------------------------------

        ops.add(new Op("dequeueTaskHi",
                       "Remove task from queue",
                       "<html>Removes this task from the queue. The task will not be<br>"+
                        "run.  It's raw <tt>Runnable</tt> object will be left in s3." +
                        "</html>",
                       null,
                       MenuType.singleTaskAction));
        ops.add(new Op("deleteTaskHi",
                       "Delete task",
                       "<html>Removes this task from the queue and deletes its raw<br>" +
                       "<tt>Runnable</tt> object from s3. The task will not be run and<br>" +
                       "cannot be re-queued.</html>",
                       null,
                       MenuType.singleTaskAction));
        ops.add(new Op("clearTaskHi",
                       "Clear task",
                       "Removes this task from the task table.",
                       null,
                       MenuType.singleTaskAction));
        ops.add(new Op("requeueTaskHi",
                       "Re-queue task",
                       "<html>Puts this task back into the queue. After it is<br>" +
                       "re-queued, it will be run (when a compute node is available.</html>",
                       null,
                       MenuType.singleTaskAction));

            // ---------- Single node ops -------------------------------------

        // clear, terminate, reboot

        ops.add(new Op("clearNodeHi",
                "Clear node",
                "<html>Removes the selected node from this table.<br>" +
                "This operation does not effect the host.</html>",
                null,
                MenuType.singleNodeAction));

        ops.add(new Op("terminateInstanceHi",
                "Terminate instance",
                "<html>Terminates the ec2 instance associated with the selected node.<br>" +
                "<br>(If a task is running on this node, it will become an ophan.</html>",
                null,
                MenuType.singleNodeAction));

        ops.add(new Op("rebootInstanceHi",
                "Reboot instance",
                "<html>Reboots the ec2 instance associated with the selected node.<br>" +
                "<br>(If a task is running on this node, it will become an ophan.</html>",
                null,
                MenuType.singleNodeAction));

            // ---------------------------------------------------------

        for (Op op : ops) {
            opHt.put(op.actionCommand, op);
        }
    }

        // -------------------------------------------------------------

    private void handleTaskSelection(String taskName,MouseEvent e) {

        TaskState s = taskStateTable.getTaskState(taskName);
        if ( s == null ) return;
        
        synchronized(popupCommandTask) {
            popupCommandTask.s = s;
        }
        
        if ( TaskState.Status.waiting.equals(s.status) )
            showDequeueDeletePopupMenu(e);
        else if ( TaskState.Status.finished.equals(s.status) )
            showClearPopupMenu(e);
        else if ( TaskState.Status.missingRunnable.equals(s.status) )
            showClearDeletePopupMenu(e);
        else if ( TaskState.Status.orphan.equals(s.status) )
            showRequeueClearDeletePopupMenu(e);
        else if ( TaskState.Status.running.equals(s.status) )
            showNoOpMessage(new String[] {
                "The task you selected ",
                "   " + s.taskName,
                " is running on " + s.host,
                "  ",
                "No operations can be performed on a task while it is running"});
            
        else if ( TaskState.Status.orphan.equals(s.status) )
            showRequeueClearDeletePopupMenu(e);
        else if ( TaskState.Status.terminatedByException.equals(s.status) )
            showRequeueClearDeletePopupMenu(e);
        else if ( TaskState.Status.unrunnable.equals(s.status) )
            showClearPopupMenu(e);
        else {
            synchronized(popupCommandTask) {
                popupCommandTask.s = null;
            }
        }
    }    

    private void showTaskPopupMenu(Op[] pOps, MouseEvent e) {
        JPopupMenu m = new JPopupMenu();
        for ( Op op : pOps ) {            
            m.add(createMenuItem(op.actionCommand,op.menuItemName,op.toolTip));
        }
        m.show(e.getComponent(),e.getX(),e.getY());        
    }


        /** Contains the state of a task seleced from the task table. It
         *  is a class because we want to synchronize access to the single
         *  instance of this variable.
         */
    private class PopupCommandTask {
        TaskState s;
        PopupCommandTask(TaskState s) { this.s = s; }
        PopupCommandTask() {}
    }

    private void showDequeueDeletePopupMenu(MouseEvent e) {
        Op[] pOps = new Op[] { opHt.get(singleTaskAC("dequeueTaskHi")),
                               opHt.get(singleTaskAC("deleteTaskHi")) };
        showTaskPopupMenu(pOps,e);
    }

    private void showClearPopupMenu(MouseEvent e) {
        Op[] pOps = new Op[] { opHt.get(singleTaskAC("clearTaskHi")) };
        showTaskPopupMenu(pOps,e);
    }

    private void showClearDeletePopupMenu(MouseEvent e) {
        Op[] pOps = new Op[] { opHt.get(singleTaskAC("clearTaskHi")),
                               opHt.get(singleTaskAC("deleteTask")) };
        showTaskPopupMenu(pOps,e);
    }

    private void showRequeueClearDeletePopupMenu(MouseEvent e) {
        Op[] pOps = new Op[] { opHt.get(singleTaskAC("requeueTaskHi")),
                               opHt.get(singleTaskAC("deleteTaskHi")),
                               opHt.get(singleTaskAC("clearTaskHi")) };
        showTaskPopupMenu(pOps,e);
    }

    private void showNoOpMessage(String[] message) {
        GuiUtil.notice(message,"No operations allowed");
    }

        // ---------------------------------------------------

    void handleNodeSelection(String hostName,MouseEvent e) {

        NodeState s = nodeStateTable.getNode(hostName);
        if ( s == null ) return;

        synchronized(popupCommandNode) {
            popupCommandNode.s = s;
        }
     
        if ( s.isOnEc2Instance() ) {
            if ( NodeState.Status.nil.equals(s.status) )
                showClearNodePopupMenu(e);
            else if ( NodeState.Status.idle.equals(s.status) ||
                      NodeState.Status.active.equals(s.status) ||
                      NodeState.Status.no_pulse.equals(s.status) )
                showClearRebootTerminatePopupmenu(e);
            else if ( NodeState.Status.nil.equals(s.status) ) {
                if ( NodeState.InstanceState.nil.equals(s.instanceState) )
                    showClearNodePopupMenu(e);
                else if (NodeState.InstanceState.running.equals(s.instanceState))
                    showClearRebootTerminatePopupmenu(e);
                else if (NodeState.InstanceState.pending.equals(s.instanceState))
                    showClearTerminatePopupMenu(e);
                else synchronized(popupCommandNode) { popupCommandNode.s = null; }
            }
            else synchronized(popupCommandNode) { popupCommandNode.s = null; }
        }
        else {
            showClearNodePopupMenu(e);
        }
    }

    private void showClearNodePopupMenu(MouseEvent e) {
        Op[] pOps = new Op[] { opHt.get(singleNodeAC("clearNodeHi")) };
        showNodePopupMenu(pOps,e);
    }

    private void showClearRebootTerminatePopupmenu(MouseEvent e) {
        Op[] pOps = new Op[] {
            opHt.get(singleNodeAC("clearNodeHi")),
            opHt.get(singleNodeAC("rebootInstanceHi")),
            opHt.get(singleNodeAC("terminateInstanceHi")),
        };
        showNodePopupMenu(pOps,e);
    }

    private void showClearTerminatePopupMenu(MouseEvent e) {
        Op[] pOps = new Op[] {
            opHt.get(singleNodeAC("clearNodeHi")),
            opHt.get(singleNodeAC("terminatedInstanceHi")),
        };
        showNodePopupMenu(pOps,e);
    }

        /** Contains the state of a node seleced from the node table. It
         *  is a class because we want to synchronize access to the single
         *  instance of this variable.
         */
    private class PopupCommandNode {
        NodeState s;
        PopupCommandNode() {}
    }

    private void showNodePopupMenu(Op[] pOps, MouseEvent e) {
        JPopupMenu m = new JPopupMenu();
        for ( Op op : pOps ) {            
            m.add(createMenuItem(op.actionCommand,op.menuItemName,op.toolTip));
        }
        m.show(e.getComponent(),e.getX(),e.getY());
    }
}
